/******************************************************************************* * Copyright (c) 2008, 2017 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation ******************************************************************************/ package org.eclipse.ui.tests.concurrency; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.core.runtime.ILogListener; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.jobs.ILock; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.internal.WorkbenchPlugin; import junit.framework.TestCase; /** * This tests the simple traditional deadlock of a thread holding a lock trying * to perform a syncExec, while the UI thread is waiting for that lock. * UISynchronizer and UILockListener conspire to prevent deadlock in this case. */ public class SyncExecWhileUIThreadWaitsForLock extends TestCase { private List<IStatus> reportedErrors; private ILogListener listener; @Override protected void setUp() throws Exception { super.setUp(); reportedErrors = new ArrayList<>(); listener = (status, plugin) -> reportedErrors.add(status); WorkbenchPlugin.getDefault().getLog().addLogListener(listener); } @Override protected void tearDown() throws Exception { if (listener != null) { WorkbenchPlugin.getDefault().getLog().removeLogListener(listener); } super.tearDown(); } public void testDeadlock() { if (Thread.interrupted()) { fail("Thread was interrupted at start of test"); } final ILock lock = Job.getJobManager().newLock(); final boolean[] blocked = new boolean[] {false}; final boolean[] lockAcquired= new boolean[] {false}; Thread locking = new Thread("SyncExecWhileUIThreadWaitsForLock") { @Override public void run() { try { //first make sure this background thread owns the lock lock.acquire(); //spawn an asyncExec that will cause the UI thread to be blocked Display.getDefault().asyncExec(() -> { blocked[0] = true; lock.acquire(); lock.release(); blocked[0] = false; }); //wait until the UI thread is blocked waiting for the lock while (!blocked[0]) { try { Thread.sleep(100); } catch (InterruptedException e) { } } //now attempt to do a syncExec that also acquires the lock //this should succeed even while the above asyncExec is blocked, thanks to UISynchronizer Display.getDefault().syncExec(() -> { try { // use a timeout to avoid deadlock in case of regression if (lock.acquire(60000)) { // this flag is used to verify that we actually acquired the lock lockAcquired[0] = true; lock.release(); } } catch (InterruptedException e) { } }); } finally { lock.release(); } } }; locking.start(); //wait until we succeeded to acquire the lock in the UI thread long waitStart = System.currentTimeMillis(); Display display = Display.getDefault(); while (!lockAcquired[0]) { //spin event loop so that asyncExed above gets run if (!display.readAndDispatch()) { display.sleep(); } //if we waited too long, fail the test if (System.currentTimeMillis()-waitStart > 60000) { assertTrue("Deadlock occurred", false); } } //if we get here, the test succeeded assertEquals("Unexpected error count reported: " + reportedErrors, 1, reportedErrors.size()); MultiStatus status = (MultiStatus) reportedErrors.get(0); assertEquals("Unexpected child status count reported: " + Arrays.toString(status.getChildren()), 2, status.getChildren().length); if (Thread.interrupted()) { // TODO: re-enable this check after bug 505920 is fixed // fail("Thread was interrupted at end of test"); } } }